﻿using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

using ProgramMain.Google;

namespace ProgramMain.Layers
{
    public class MapLayer : GraphicLayer
    {
        private const int MaxCacheSize = 240;

        private Rectangle GoogleBlockView;
        private Bitmap EmptyBlock = null;       

        protected class MapWorkerEvent : WorkerEvent
        {
            public GoogleBlock Block;

            public MapWorkerEvent(WorkerEventType pEventType, GoogleBlock pBlock, EventPriorityType PriorityType)
                : base(pEventType, true, PriorityType)
            {
                Block = pBlock;
            }
            
            override public int CompareTo(object obj)
            {
                int res = base.CompareTo(obj);
                if (res == 0)
                {
                    if (obj is MapWorkerEvent)
                    {
                        res = ((MapWorkerEvent) obj).Block.CompareTo(this.Block);
                    }
                    else
                    {
                        res = -1;
                    }
                }
                return res;
            }
        }

        private struct MapCacheItem
        {
            public long Timestamp;
            public Bitmap bmp;
        };

        private static SortedDictionary<GoogleBlock, MapCacheItem> mapCache = new SortedDictionary<GoogleBlock, MapCacheItem>();

        public MapLayer(int pWidth, int pHeight, Coordinate pCenterCoordinate, int pLevel, Control pDelegateControl, PixelFormat pPIFormat)
            : base(pWidth, pHeight, pCenterCoordinate, pLevel, pDelegateControl, pPIFormat)
        {
            EmptyBlock = CreateCompatibleBitmap(null, GoogleBlock.BlockSize, GoogleBlock.BlockSize, pPIFormat);
        }

        override protected void TranslateCoords()
        {
            base.TranslateCoords();

            // Определяем видимый блок битмапа по оси X Y
            GoogleBlockView = GoogleScreenView.GetGoogleBlockView();
        }
        
        private void PutMapThreadEvent(WorkerEventType eventType, GoogleBlock block, EventPriorityType priorityType)
        {
            PutWorkerThreadEvent(new MapWorkerEvent(eventType, block, priorityType));
        }

        override protected void DrawLayer(Rectangle ClipRectangle)
        {
            try
            {
                SwapDrawBuffer();

                Rectangle LocalBlockView = new Rectangle(GoogleBlockView.Location, GoogleBlockView.Size);
                GoogleRectangle LocalScreenView = (GoogleRectangle)GoogleScreenView.Clone();
                
                while (DrawImages(LocalBlockView, LocalScreenView) == false)
                {
                    DropWorkerThreadEvents(WorkerEventType.RedrawLayer);
                    
                    LocalBlockView = new Rectangle(GoogleBlockView.Location, GoogleBlockView.Size);
                    LocalScreenView = (GoogleRectangle)GoogleScreenView.Clone();
                }
            }
            finally
            {
                SwapDrawBuffer();
            }
            FireIvalidateLayer(ClipRectangle);
        }

        private bool DrawImages(Rectangle LocalBlockView, GoogleRectangle LocalScreenView)
        {
            for (int X = LocalBlockView.Left; X <= LocalBlockView.Right; X++)
            {
                for (int Y = LocalBlockView.Top; Y <= LocalBlockView.Bottom; Y++)
                {
                    GoogleBlock block = new GoogleBlock(X, Y, Level);
                    Point pt = ((GoogleCoordinate)block).GetScreenPoint(LocalScreenView);

                    Bitmap bmp = FindImage(block);
                    if (bmp != null)
                    {
                        DrawBitmap(bmp, pt);
                    }
                    else
                    {
                        DrawBitmap(EmptyBlock, pt);
                        PutMapThreadEvent(WorkerEventType.DownloadImage, block, EventPriorityType.Idle);
                    }

                    if (Terminating) return true;

                    if (LocalScreenView.CompareTo(GoogleScreenView) != 0) return false;
                }
            }
            return true;
        }

        private static bool PointContains(Point pt, Rectangle rect)
        {
            //!!!стандартная функция не работает на граничных условиях
            return pt.X >= rect.Left && pt.X <= rect.Right
                && pt.Y >= rect.Top && pt.Y <= rect.Bottom;
        }

        private void DrawImage(GoogleBlock block)
        {
            if (Terminating) return;

            if (block.Level == Level && PointContains(block.Pt, GoogleBlockView))
            {
                Bitmap bmp = FindImage(block);
                if (bmp != null)
                {
                    Rectangle rect = ((GoogleRectangle)block).GetScreenRect(GoogleScreenView);
                    DrawBitmap(bmp, rect.Location);

                    FireIvalidateLayer(rect);
                }
            }
        }

        private Bitmap FindImage(GoogleBlock block)
        {
            if (mapCache.ContainsKey(block))
            {
                MapCacheItem dimg;
                dimg = mapCache[block];
                dimg.Timestamp = DateTime.Now.Ticks;
                return dimg.bmp;
            }
            return null;
        }

        protected override bool SetCenterCoordinate(Coordinate center, int level)
        {
            bool res = base.SetCenterCoordinate(center, level);
            
            if (res)
                DropWorkerThreadEvents(WorkerEventType.DownloadImage);

            return res;
        }

        protected override bool DispatchThreadEvents(WorkerEvent WorkerEvent)
        {
            bool res = base.DispatchThreadEvents(WorkerEvent);

            if (!res && WorkerEvent is MapWorkerEvent)
            {
                switch (WorkerEvent.EventType)
                {
                    case WorkerEventType.DownloadImage:
                        {
                            DownloadImage(((MapWorkerEvent)WorkerEvent).Block);
                            return true;
                        }
                    case WorkerEventType.DrawImage:
                        {
                            DrawImage(((MapWorkerEvent)WorkerEvent).Block);
                            return true;
                        }
                }
            }

            return res;
        }

        private void DownloadImage(GoogleBlock block)
        {
            if (mapCache.ContainsKey(block))
            {
                MapCacheItem dimg = mapCache[block];
                dimg.Timestamp = DateTime.Now.Ticks;
            }
            else
            {
                Bitmap bmp = null;
                if (!Properties.Settings.Default.DownloadImagesFromGoogle)
                {
                    bmp = DownloadImageFromFile(block);
                }
                else
                {
                    if (Properties.Settings.Default.WriteMapCache)
                    {
                        bmp = DownloadImageFromFile(block);
                    }
                    if (bmp == null)
                    {
                        bmp = DownloadImageFromGoogle(block);
                    }
                }

                if (bmp != null)
                {
                    MapCacheItem dimg = new MapCacheItem();
                    dimg.Timestamp = DateTime.Now.Ticks;
                    dimg.bmp = bmp;
                    mapCache[block] = dimg;

                    TruncateImageCache(block);

                    PutMapThreadEvent(WorkerEventType.DrawImage, block, EventPriorityType.Low);
                }
            }
        }

        private void TruncateImageCache(GoogleBlock newCacheItem)
        {
            while (mapCache.Count > MaxCacheSize)
            {
                GoogleBlock mt = GoogleBlock.Empty;
                long lTicks = DateTime.Now.Ticks;

                foreach (KeyValuePair<GoogleBlock, MapCacheItem> lt in mapCache)
                {
                    if (lt.Value.Timestamp < lTicks && lt.Key.CompareTo(newCacheItem) != 0)
                    {
                        mt = lt.Key;
                        lTicks = lt.Value.Timestamp;
                    }
                }

                if (mt != GoogleBlock.Empty)
                    mapCache.Remove(mt);
            }
        }

        private Bitmap DownloadImageFromGoogle(GoogleBlock block)
        {
            try
            {
                HttpWebRequest oRequest = MapUtilities.CreateGoogleWebRequest(block);
                HttpWebResponse oResponse = (HttpWebResponse) oRequest.GetResponse();

                Stream bmpStream = new MemoryStream();
                oResponse.GetResponseStream().CopyTo(bmpStream);
                oResponse.Close();
                if (bmpStream != null)
                {
                    List<Task> tasks = new List<Task>();

                    if (Properties.Settings.Default.DownloadImagesFromGoogle
                        && Properties.Settings.Default.WriteMapCache)
                    {
                        tasks.Add(Task.Factory.StartNew(delegate { WriteImageToFile(block, bmpStream); }));
                    }

                    Bitmap bmp = null;

                    tasks.Add(Task.Factory.StartNew(delegate
                    {
                        bmp = (Bitmap)Image.FromStream(bmpStream);
                        bmp = CreateCompatibleBitmap(bmp, GoogleBlock.BlockSize, GoogleBlock.BlockSize, PIFormat);
                    }));

                    Task.WaitAll(tasks.ToArray());

                    return bmp;
                }
            }
            catch (Exception)
            {
                //do nothing
            }
            return null;
        }

        private Bitmap DownloadImageFromFile(GoogleBlock block)
        {
            try
            {
                string fileName = Properties.Settings.GetMapFileName(block);
                if (File.Exists(fileName))
                {
                    Bitmap bmp = (Bitmap)Image.FromFile(fileName);

                    return CreateCompatibleBitmap(bmp, PIFormat);
                }
            }
            catch (Exception)
            {
                //do nothing
            }
            return null;
        }

        private void WriteImageToFile(GoogleBlock block, Stream bmpStream)
        {
            string fileName = Properties.Settings.GetMapFileName(block);
            try
            {
                if (!File.Exists(fileName))
                {
                    DirectoryInfo destdir = new DirectoryInfo(Path.GetDirectoryName(fileName));
                    if (!destdir.Exists)
                    {
                        destdir.Create();
                    }
                    FileStream fileStream = File.Create(fileName);
                    try
                    {
                        bmpStream.Seek(0, SeekOrigin.Begin);
                        bmpStream.CopyTo(fileStream);
                    }
                    finally
                    {
                        fileStream.Flush();
                        fileStream.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                //do nothing
                System.Diagnostics.Trace.WriteLine(ex.Message);
            }
        }
    }
}
